﻿using System.IO;
using System.IO.Compression;
using Microsoft.Isam.Esent.Interop;
using System;
using System.Threading;


namespace Microsoft.Isam.Esent.Serialization
{
	public partial class EseSerializer
	{
		/// <summary>Backup the database.</summary>
		/// <remarks>
		/// <para>This function saves a full DB backup while the engine and database are online and active.
		/// It uses <see href="http://msdn.microsoft.com/en-us/library/ms683094(v=EXCHG.10).aspx">JetBeginExternalBackup API</see>.</para>
		/// <para>The data storage format is either <see href="http://en.wikipedia.org/wiki/Tar_(file_format)">tar</see>,
		/// or tar.gzip.</para>
		/// </remarks>
		/// <param name="s">The destination stream to write the backup.</param>
		/// <param name="bCompress">True to compress the backup.</param>
		public void BackupDatabase( Stream s, bool bCompress )
		{
			if( bCompress )
			{
				using( GZipStream gz = new GZipStream( s, CompressionMode.Compress ) )
					BackupDatabaseImpl( gz );
			}
			else
				BackupDatabaseImpl( s );
		}

		// A large output buffer should be used to maximize backup performance.
		// Some experimentation may be required to find the right tradeoff between resource consumption and throughput for a given situation.
		// The output buffer should be no smaller than 64KB in any case.
		const int iBuffSize = 256 * 1024;

		delegate void JetListFilesDelegate( JET_INSTANCE instance, out string files, int maxChars, out int actualChars );

		void BackupFiles( TapeArchiver tarArchive, byte[] buff, JetListFilesDelegate listFilesProc )
		{
			// Call the supplied API to list the files.
			int maxChars = 1024, actualChars = 0;
			string files;
			while( true )
			{
				listFilesProc( idInstance, out files, maxChars, out actualChars );
				if( actualChars < maxChars )
					break;
				maxChars = maxChars * 16;
				actualChars = 0;
			}

			string[] arrFiles = files.Split( new char[ 1 ] { '\0' }, StringSplitOptions.RemoveEmptyEntries );

			// Backup the files.
			foreach( string strFileName in arrFiles )
			{
				JET_HANDLE hFile;
				long fsLow, fsHigh;
				Api.JetOpenFileInstance( idInstance, strFileName, out hFile, out fsLow, out fsHigh );

				ulong ulFileSize = ( (ulong)fsLow & (ulong)uint.MaxValue ) | ( ( (ulong)fsHigh & (ulong)uint.MaxValue ) << 32 );

				try
				{
					ulong ulRemaining = ulFileSize;

					string shortName = Path.GetFileName( strFileName );
					using( var tarFile = tarArchive.AddFile( shortName, ulFileSize ) )
					{
						while( ulRemaining > 0 )
						{
							int cbRead;
							Api.JetReadFileInstance( idInstance, hFile, buff, iBuffSize, out cbRead );
							if( 0 == cbRead )
								throw new EndOfStreamException( "Error backing up file " + strFileName );
							tarFile.Write( buff, cbRead );
							ulRemaining -= (ulong)cbRead;
						}
					}
				}
				finally
				{
					Api.JetCloseFileInstance( idInstance, hFile );
				}
			}
		}

		void BackupDatabaseImpl( Stream s )
		{
			// Prepare the TAR writer and the buffer
			TapeArchiver tarArchive = new TapeArchiver( s );
			byte[] buff = new byte[ iBuffSize ];

			// Start the backup process
			Api.JetBeginExternalBackupInstance( idInstance, BeginExternalBackupGrbit.None );

			// Database files that should become part of the backup file set
			BackupFiles( tarArchive, buff, Api.JetGetAttachInfoInstance );

			// Database patch files and transaction log files that should become part of the backup file set
			BackupFiles( tarArchive, buff, Api.JetGetLogInfoInstance );

			// Delete any transaction log files that will no longer be needed once the current backup completes successfully.
			Api.JetTruncateLogInstance( idInstance );

			// End an external backup session
			Api.JetEndExternalBackupInstance( idInstance );

			tarArchive.CloseArchive();
		}

		/* internal class BackupThread
		{
			byte[][] buffers = new byte[][]
			{
				new byte[ iBuffSize ],
				new byte[ iBuffSize ]
			};

			volatile int nextBackup = 0, nextClient = 0;
			Semaphore _sem = new Semaphore( 0, 2 );

			// Called from any thread
			byte[] getBuffer(int iBuff)
			{
				return buffers[ iBuff ];
			}

			// Called by the backup thread, signaling that it has some new data to store.
			int WriteToStream( int iBuff, int cbRead )
			{
				byte[] data = buffers[ iBuff ];

				// TODO: notify & wait?

				return ( iBuff ^ 1 );
			}

			void ThreadProc()
			{
				
			}

			public void Run( Stream s )
			{
				TapeArchiver tarArchive = new TapeArchiver( s );
				Thread t = new Thread( ThreadProc );
				t.Start();

				
			}
		}

		void BackupDatabaseImpl2( Stream s )
		{
			
		} */
	}
}